package com.q4labs.gridsquare;

import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.List;

import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.widget.Toast;

public class GridSquare extends Activity implements LocationListener {

	private static final String TAG = "GridSquare";
	private static final int TWO_MINUTES = 1000 * 60 * 2;
	
	private TextView latituteField;
	private TextView longitudeField;
	private TextView gridField;
	private TextView accuracyField;
	private TextView providerField;
	private TextView fixField;
	private TextView fixCountField;
	private AlertDialog gpsAlert;
	private LocationManager locationManager;
	private String provider;
	private String lastGridSquare;
	
	private Integer badFixCount = 0;
	private Location currentLocation;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_grid_square);
        
        latituteField = (TextView) findViewById(R.id.locationLatitude);
        longitudeField = (TextView) findViewById(R.id.locationLongitude);
        gridField = (TextView) findViewById(R.id.locationGrid);
        accuracyField = (TextView) findViewById(R.id.locationAccuracy);
        providerField = (TextView) findViewById(R.id.locationProvider);
        fixField = (TextView) findViewById(R.id.locationFix);
        fixCountField = (TextView) findViewById(R.id.locationFixCount);
        
        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
        
        checkForGPS(locationManager);
    }
    
    private boolean initializeLocation(){
    	Location location = getBestPrevious(locationManager);
        printLocation(location);
        
        // Initialize the location fields
        if (location != null) {
        	onLocationChanged(location);
        } else {
        	latituteField.setText("Location not available");
        	longitudeField.setText("Location not available");
        	gridField.setText("Not available");
        	providerField.setText("None");
        	accuracyField.setText("Unknown");
        	fixField.setText("Never");
        	fixCountField.setText(badFixCount.toString());
        }
        
        return true;
    }
    
    private Location getBestPrevious(LocationManager locationManager){
    	Criteria criteria = new Criteria();
    	criteria.setAccuracy(Criteria.ACCURACY_FINE);
    	provider = locationManager.getBestProvider(criteria, true);    	
        logWrite("Best Provider: " + provider);
        if (provider == null){
        	warnNoProviders();
        	return null;
        }
        
        return locationManager.getLastKnownLocation(provider);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_grid_square, menu);
        return true;
    }
    
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
            case R.id.itemPause:
                
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
    
    private static String Grid(Double lat, Double lon){
    	lon += 180;
    	lat += 90;
    	
    	String grid = "";
    	
    	grid += chr(ord('A') + (int)Math.floor(lon / 20));
    	grid += chr(ord('A') + (int)Math.floor(lat / 10));
    	grid += chr(ord('0') + (int)Math.floor((lon % 20)/2));
    	grid += chr(ord('0') + (int)Math.floor((lat % 10)/1));
    	grid += chr(ord('a') + (int)Math.floor((lon - (Math.floor(lon/2)*2)) / (5.0/60)));
    	grid += chr(ord('a') + (int)Math.floor((lat - (Math.floor(lat/1)*1)) / (2.5/60)));
    	
    	return grid;
    }
    
    private static int ord(char letter){
    	return (int)letter;
    }
    
    private static char chr(int value){
    	return (char)value;
    }
    
    /* Request updates at startup */
    @Override
    protected void onResume() {
      super.onResume();
      if (initializeLocation()){
    	  logWrite("Resuming location updates.");
    	  List<String> providers = locationManager.getProviders(true);
    	  for (String prov: providers){
    		  if (prov.equals("passive")){
    			  logWrite("Skipping passive.");
    			  continue;
    		  }
    		  logWrite("Requesting updates from: " + prov);
    		  locationManager.requestLocationUpdates(prov, 400, 1, this);
    	  }
	      
      }
    }

    /* Remove the locationlistener updates when Activity is paused */
    @Override
    protected void onPause() {
      super.onPause();
      locationManager.removeUpdates(this);
      logWrite("Suspending location updates.");
    }

    @Override
    public void onLocationChanged(Location location) {
    	printLocation(location);
    	
    	badFixCount++;
    	fixCountField.setText(badFixCount.toString());
    	
    	if (!isBetterLocation(location, currentLocation)){
    		return;
    	}
    	
    	provider = location.getProvider();
    	currentLocation = location;
    	
		Double lat = location.getLatitude();
		Double lng = location.getLongitude();
		float accuracy = location.getAccuracy();
		Date fixTime = new Date(location.getTime());
		SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss");
		
		latituteField.setText(String.format("%.5f", lat));
		longitudeField.setText(String.format("%.5f", lng));
		accuracyField.setText(String.format("%.2f", accuracy) + "m");
		providerField.setText(provider);
		fixField.setText(ft.format(fixTime));
		
		String grid = Grid(lat, lng);
		if (!grid.equals(lastGridSquare)){
			enteredNewGrid(lat, lng, grid);
		}
		lastGridSquare = grid;
		gridField.setText(grid);
		
    }
    
    private void enteredNewGrid(Double lat, Double lon, String grid){
    	//Toast.makeText(this, "Entered new grid.", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
    	logWrite("Provider status change: " + provider + "; Status: " + status);
    	for (String key: extras.keySet()){
    		logWrite(key + ": " + extras.get(key));
    	}
    }

    @Override
    public void onProviderEnabled(String provider) {
    	logWrite("Enabled new provider: " + provider);
    	Toast.makeText(this, "Enabled new provider " + provider, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onProviderDisabled(String provider) {
    	logWrite("Disabled provider: " + provider);
    	Toast.makeText(this, "Disabled provider " + provider, Toast.LENGTH_SHORT).show();
    }
    
    private static void printLocation(Location location){
    	if (location == null){
    		logWrite("Location: [UNKNOWN]");
    	} else {
    		logWrite("Location: " + location.toString());
    	}
    }
    
    private static void logWrite(String message){
    	if (BuildConfig.DEBUG) {
    		android.util.Log.i(TAG, message);
    	}
    }
    
    private void warnNoProviders(){
    	if (gpsAlert != null){
    		// No need to show the GPS warning if there are no providers at all.
    		gpsAlert.cancel();
            gpsAlert = null;
    	}
    	
    	AlertDialog.Builder builder = new AlertDialog.Builder(this);
    	builder.setMessage("There are no location sources enabled. Would you like to enable one?")
    	       .setCancelable(false)
    	       .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
    	           public void onClick(DialogInterface dialog, int id) {
    	        	   Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    	   				startActivity(intent);
    	           }
    	       })
    	       .setNegativeButton("No", new DialogInterface.OnClickListener() {
    	           public void onClick(DialogInterface dialog, int id) {
    	                finish();
    	           }
    	       });
    	AlertDialog alert = builder.create();
    	alert.show();
    }
    
    private void checkForGPS(LocationManager locationManager){
    	// Check if the GPS is enabled and if not, ask if they'd like to enable it.
    	// If they agree, send them to the GPS settings.
    	boolean enabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
    	logWrite("GPS enabled? " + enabled);
		if (!enabled) {
			AlertDialog.Builder builder = new AlertDialog.Builder(this);
	    	builder.setMessage("Your GPS is not enabled. Would you like to enable it for a more accurate location?")
	    	       .setCancelable(false)
	    	       .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
	    	           public void onClick(DialogInterface dialog, int id) {
	    	        	   Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
	    	   				startActivity(intent);
	    	   				gpsAlert = null;
	    	           }
	    	       })
	    	       .setNegativeButton("No", new DialogInterface.OnClickListener() {
	    	           public void onClick(DialogInterface dialog, int id) {
	    	                dialog.cancel();
	    	                gpsAlert = null;
	    	           }
	    	       });
	    	gpsAlert = builder.create();
	    	gpsAlert.show();
		}
    }

    /** Determines whether one Location reading is better than the current Location fix
      * @param location  The new Location that you want to evaluate
      * @param currentBestLocation  The current Location fix, to which you want to compare the new one
      */
    protected boolean isBetterLocation(Location location, Location currentBestLocation) {
        if (currentBestLocation == null) {
            // A new location is always better than no location
            return true;
        }

        // Check whether the new location fix is newer or older
        long timeDelta = location.getTime() - currentBestLocation.getTime();
        boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
        boolean isNewer = timeDelta > 0;

        // If it's been more than two minutes since the current location, use the new location
        // because the user has likely moved
        if (isSignificantlyNewer) {
            return true;
        // If the new location is more than two minutes older, it must be worse
        } else if (isSignificantlyOlder) {
            return false;
        }

        // Check whether the new location fix is more or less accurate
        int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;

        // Check if the old and new location are from the same provider
        boolean isFromSameProvider = isSameProvider(location.getProvider(),
                currentBestLocation.getProvider());

        // Determine location quality using a combination of timeliness and accuracy
        if (isMoreAccurate) {
            return true;
        } else if (isNewer && !isLessAccurate) {
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true;
        }
        return false;
    }

    /** Checks whether two providers are the same */
    private boolean isSameProvider(String provider1, String provider2) {
        if (provider1 == null) {
          return provider2 == null;
        }
        return provider1.equals(provider2);
    }
}
